'use strict'; // necessary for es6 output in node

import {browser, by, element, ElementArrayFinder, ElementFinder} from 'protractor';
import {promise} from 'selenium-webdriver';

const expectedH1 = 'Tour of Heroes';
const expectedTitle = `${expectedH1}`;
const targetHero = {id: 15, name: 'Magneta'};
const targetHeroDashboardIndex = 3;
const nameSuffix = 'X';
const newHeroName = targetHero.name + nameSuffix;

class Hero {
    id: number;
    name: string;

    // Factory methods

    // Hero from string formatted as '<id> <name>'.
    static fromString(s: string): Hero {
        return {
            id: +s.substr(0, s.indexOf(' ')),
            name: s.substr(s.indexOf(' ') + 1),
        };
    }

    // Hero from hero list <li> element.
    static async fromLi(li: ElementFinder): Promise<Hero> {
        let stringsFromA = await li.all(by.css('a')).getText();
        let strings = stringsFromA[0].split(' ');
        return {id: +strings[0], name: strings[1]};
    }

    // Hero id and name from the given detail element.
    static async fromDetail(detail: ElementFinder): Promise<Hero> {
        // Get hero id from the first <div>
        let _id = await detail.all(by.css('div')).first().getText();
        // Get name from the h2
        let _name = await detail.element(by.css('h2')).getText();
        return {
            id: +_id.substr(_id.indexOf(' ') + 1),
            name: _name.substr(0, _name.lastIndexOf(' ')),
        };
    }
}

// TODO(i): temorarily disable these tests because angular-in-memory-web-api is not compatible with rxjs v6 yet
//          and we don't have the backwards compatibility package yet.
//          Reenable after rxjs v6 compatibility package is out or angular-in-memory-web-api is compatible with rxjs v6
xdescribe('Tutorial part 6', () => {

    beforeAll(() => browser.get(''));

    function getPageElts() {
        let navElts = element.all(by.css('app-root nav a'));

        return {
            navElts: navElts,

            appDashboardHref: navElts.get(0),
            appDashboard: element(by.css('app-root app-dashboard')),
            topHeroes: element.all(by.css('app-root app-dashboard > div h4')),

            appHeroesHref: navElts.get(1),
            appHeroes: element(by.css('app-root app-heroes')),
            allHeroes: element.all(by.css('app-root app-heroes li')),
            selectedHeroSubview: element(by.css('app-root app-heroes > div:last-child')),

            heroDetail: element(by.css('app-root app-hero-detail > div')),

            searchBox: element(by.css('#search-box')),
            searchResults: element.all(by.css('.search-result li')),
        };
    }

    describe('Initial page', () => {

        it(`has title '${expectedTitle}'`, () => {
            expect(browser.getTitle()).toEqual(expectedTitle);
        });

        it(`has h1 '${expectedH1}'`, () => {
            expectHeading(1, expectedH1);
        });

        const expectedViewNames = ['Dashboard', 'Heroes'];
        it(`has views ${expectedViewNames}`, () => {
            let viewNames = getPageElts().navElts.map((el: ElementFinder) => el.getText());
            expect(viewNames).toEqual(expectedViewNames);
        });

        it('has dashboard as the active view', () => {
            let page = getPageElts();
            expect(page.appDashboard.isPresent()).toBeTruthy();
        });

    });

    describe('Dashboard tests', () => {

        beforeAll(() => browser.get(''));

        it('has top heroes', () => {
            let page = getPageElts();
            expect(page.topHeroes.count()).toEqual(4);
        });

        it(`selects and routes to ${targetHero.name} details`, dashboardSelectTargetHero);

        it(`updates hero name (${newHeroName}) in details view`, updateHeroNameInDetailView);

        it(`cancels and shows ${targetHero.name} in Dashboard`, () => {
            element(by.buttonText('go back')).click();
            browser.waitForAngular(); // seems necessary to gets tests to pass for learn-augular-demo-tour-of-heroes

            let targetHeroElt = getPageElts().topHeroes.get(targetHeroDashboardIndex);
            expect(targetHeroElt.getText()).toEqual(targetHero.name);
        });

        it(`selects and routes to ${targetHero.name} details`, dashboardSelectTargetHero);

        it(`updates hero name (${newHeroName}) in details view`, updateHeroNameInDetailView);

        it(`saves and shows ${newHeroName} in Dashboard`, () => {
            element(by.buttonText('save')).click();
            browser.waitForAngular(); // seems necessary to gets tests to pass for learn-augular-demo-tour-of-heroes

            let targetHeroElt = getPageElts().topHeroes.get(targetHeroDashboardIndex);
            expect(targetHeroElt.getText()).toEqual(newHeroName);
        });

    });

    describe('Heroes tests', () => {

        beforeAll(() => browser.get(''));

        it('can switch to Heroes view', () => {
            getPageElts().appHeroesHref.click();
            let page = getPageElts();
            expect(page.appHeroes.isPresent()).toBeTruthy();
            expect(page.allHeroes.count()).toEqual(10, 'number of heroes');
        });

        it('can route to hero details', async () => {
            getHeroLiEltById(targetHero.id).click();

            let page = getPageElts();
            expect(page.heroDetail.isPresent()).toBeTruthy('shows hero detail');
            let hero = await Hero.fromDetail(page.heroDetail);
            expect(hero.id).toEqual(targetHero.id);
            expect(hero.name).toEqual(targetHero.name.toUpperCase());
        });

        it(`updates hero name (${newHeroName}) in details view`, updateHeroNameInDetailView);

        it(`shows ${newHeroName} in Heroes list`, () => {
            element(by.buttonText('save')).click();
            browser.waitForAngular();
            let expectedText = `${targetHero.id} ${newHeroName}`;
            expect(getHeroAEltById(targetHero.id).getText()).toEqual(expectedText);
        });

        it(`deletes ${newHeroName} from Heroes list`, async () => {
            const heroesBefore = await toHeroArray(getPageElts().allHeroes);
            const li = getHeroLiEltById(targetHero.id);
            li.element(by.buttonText('x')).click();

            const page = getPageElts();
            expect(page.appHeroes.isPresent()).toBeTruthy();
            expect(page.allHeroes.count()).toEqual(9, 'number of heroes');
            const heroesAfter = await toHeroArray(page.allHeroes);
            // console.log(await Hero.fromLi(page.allHeroes[0]));
            const expectedHeroes = heroesBefore.filter(h => h.name !== newHeroName);
            expect(heroesAfter).toEqual(expectedHeroes);
            // expect(page.selectedHeroSubview.isPresent()).toBeFalsy();
        });

        it(`adds back ${targetHero.name}`, async () => {
            const newHeroName = 'Alice';
            const heroesBefore = await toHeroArray(getPageElts().allHeroes);
            const numHeroes = heroesBefore.length;

            element(by.css('input')).sendKeys(newHeroName);
            element(by.buttonText('add')).click();

            let page = getPageElts();
            let heroesAfter = await toHeroArray(page.allHeroes);
            expect(heroesAfter.length).toEqual(numHeroes + 1, 'number of heroes');

            expect(heroesAfter.slice(0, numHeroes)).toEqual(heroesBefore, 'Old heroes are still there');

            const maxId = heroesBefore[heroesBefore.length - 1].id;
            expect(heroesAfter[numHeroes]).toEqual({id: maxId + 1, name: newHeroName});
        });

        it('displays correctly styled buttons', async () => {
            element.all(by.buttonText('x')).then(buttons => {
                for (const button of buttons) {
                    // Inherited styles from styles.css
                    expect(button.getCssValue('font-family')).toBe('Arial');
                    expect(button.getCssValue('border')).toContain('none');
                    expect(button.getCssValue('padding')).toBe('5px 10px');
                    expect(button.getCssValue('border-radius')).toBe('4px');
                    // Styles defined in heroes.component.css
                    expect(button.getCssValue('left')).toBe('194px');
                    expect(button.getCssValue('top')).toBe('-32px');
                }
            });

            const addButton = element(by.buttonText('add'));
            // Inherited styles from styles.css
            expect(addButton.getCssValue('font-family')).toBe('Arial');
            expect(addButton.getCssValue('border')).toContain('none');
            expect(addButton.getCssValue('padding')).toBe('5px 10px');
            expect(addButton.getCssValue('border-radius')).toBe('4px');
        });

    });

    describe('Progressive hero search', () => {

        beforeAll(() => browser.get(''));

        it(`searches for 'Ma'`, async () => {
            getPageElts().searchBox.sendKeys('Ma');
            browser.sleep(1000);

            expect(getPageElts().searchResults.count()).toBe(4);
        });

        it(`continues search with 'g'`, async () => {
            getPageElts().searchBox.sendKeys('g');
            browser.sleep(1000);
            expect(getPageElts().searchResults.count()).toBe(2);
        });

        it(`continues search with 'e' and gets ${targetHero.name}`, async () => {
            getPageElts().searchBox.sendKeys('n');
            browser.sleep(1000);
            let page = getPageElts();
            expect(page.searchResults.count()).toBe(1);
            let hero = page.searchResults.get(0);
            expect(hero.getText()).toEqual(targetHero.name);
        });

        it(`navigates to ${targetHero.name} details view`, async () => {
            let hero = getPageElts().searchResults.get(0);
            expect(hero.getText()).toEqual(targetHero.name);
            hero.click();

            let page = getPageElts();
            expect(page.heroDetail.isPresent()).toBeTruthy('shows hero detail');
            let hero2 = await Hero.fromDetail(page.heroDetail);
            expect(hero2.id).toEqual(targetHero.id);
            expect(hero2.name).toEqual(targetHero.name.toUpperCase());
        });
    });

    async function dashboardSelectTargetHero() {
        let targetHeroElt = getPageElts().topHeroes.get(targetHeroDashboardIndex);
        expect(targetHeroElt.getText()).toEqual(targetHero.name);
        targetHeroElt.click();
        browser.waitForAngular(); // seems necessary to gets tests to pass for learn-augular-demo-tour-of-heroes

        let page = getPageElts();
        expect(page.heroDetail.isPresent()).toBeTruthy('shows hero detail');
        let hero = await Hero.fromDetail(page.heroDetail);
        expect(hero.id).toEqual(targetHero.id);
        expect(hero.name).toEqual(targetHero.name.toUpperCase());
    }

    async function updateHeroNameInDetailView() {
        // Assumes that the current view is the hero details view.
        addToHeroName(nameSuffix);

        let page = getPageElts();
        let hero = await Hero.fromDetail(page.heroDetail);
        expect(hero.id).toEqual(targetHero.id);
        expect(hero.name).toEqual(newHeroName.toUpperCase());
    }

});

function addToHeroName(text: string): promise.Promise<void> {
    let input = element(by.css('input'));
    return input.sendKeys(text);
}

function expectHeading(hLevel: number, expectedText: string): void {
    let hTag = `h${hLevel}`;
    let hText = element(by.css(hTag)).getText();
    expect(hText).toEqual(expectedText, hTag);
}

function getHeroAEltById(id: number): ElementFinder {
    let spanForId = element(by.cssContainingText('li span.badge', id.toString()));
    return spanForId.element(by.xpath('..'));
}

function getHeroLiEltById(id: number): ElementFinder {
    let spanForId = element(by.cssContainingText('li span.badge', id.toString()));
    return spanForId.element(by.xpath('../..'));
}

async function toHeroArray(allHeroes: ElementArrayFinder): Promise<Hero[]> {
    let promisedHeroes = await allHeroes.map(Hero.fromLi);
    // The cast is necessary to get around issuing with the signature of Promise.all()
    return <Promise<any>>Promise.all(promisedHeroes);
}
